﻿package org.neelts.web.vkontakte {
	
	import flash.events.EventDispatcher;
	
	import org.neelts.web.vkontakte.*;
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.util.Hex;
	import com.hurlant.util.Base64;
	
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.utils.ByteArray;

	/**
	* ...
	* @author Neelts © 2008
	*/
	public class APIConnector extends EventDispatcher {
		
		public var TEST_MODE:Boolean;
		public var USE_COMPRESSION:Boolean;
		
		private var VIEWER_ID:String;
		private var ID:String;
		private var SECRET:String;
		private var TEST_VIEWER:String;
		
		private var _session:int;
		private var _sessionName:String;
		
		private var reqParams:Array;
		
		private var vars:Object;
		
		public function APIConnector(vars:Object, ID:String, SECRET:String, TEST_VIEWER:String = "", TEST_MODE:Boolean = false, USE_COMPRESSION:Boolean = true) {
			
			reqParams = new Array();
			
			this.ID = ID;
			this.SECRET = SECRET;
			this.TEST_VIEWER = TEST_VIEWER;
			this.TEST_MODE = TEST_MODE;
			this.USE_COMPRESSION = USE_COMPRESSION;
			
			this.vars = vars;
			VIEWER_ID = vars.viewer ? vars.viewer : TEST_VIEWER;
			
			if (TEST_MODE) {
				VIEWER_ID = TEST_VIEWER;
				reqParams.push( { test_mode:1 } );
			}
		}
		
		public function request(type:String, options:Object = null, onComplete:Function = null):void {
			switch (type) {
				case API.PUT_VARIABLE:
					sendRequest(API.PUT_VARIABLE, new Array( { key:options.key.toString() }, { value:options.value.toString() } ));
					break;
				case API.GET_VARIABLE:
					sendRequest(API.GET_VARIABLE, new Array( { key:options.toString() } ));
					break;
				case API.GET_VARIABLES:
					sendRequest(API.GET_VARIABLES, new Array( { key:options.key.toString() }, { count:options.count.toString() } ));
					break;
				case API.GET_USER_INFO:
					sendRequest(API.GET_USER_INFO);
					break;
				case API.GET_USER_INFO_EX:
					sendRequest(API.GET_USER_INFO_EX);
					break;
				case API.GET_SERVER_TIME:
					sendRequest(API.GET_SERVER_TIME);
					break;
				case API.GET_HIGH_SCORES:
					sendRequest(API.GET_HIGH_SCORES);
					break;
				case API.SET_USER_SCORE:
					sendRequest(API.SET_USER_SCORE, new Array( { score:options.toString() } ));
					break;
				case API.GET_MESSAGES:
					sendRequest(API.GET_MESSAGES, options != null ? new Array( { messages_to_get:options.toString() } ) : null);
					break;
				case API.SEND_MESSAGE:
					
					var ms:String = options.toString();
					
					if (USE_COMPRESSION) {
						
						var ba:ByteArray = new ByteArray();
						ba.writeUTFBytes(ms);
						ba.compress();
						var b64:String = Base64.encodeByteArray(ba);
						ms = Hex.fromArray(new MD5().hash(ba)) + b64;
						
					}
					
					sendRequest(API.SEND_MESSAGE, options ? new Array({message:ms}) : null);
					break;
			}
			onComplete != null ? addEventListener(type, onComplete) : null;
		}
		
		private function sendRequest(method:String, options:Object = null):void {
			
			var response:URLLoader = new URLLoader();
			
			response.addEventListener(Event.COMPLETE, function(e:Event):void {
				
				var data:XML = new XML(e.target.data);
				
				receiveData(method, data);
				
				if (data.firstChild.nodeName == "error") {
					dispatchEvent(new APIEvent(APIEvent.ERROR, data.firstChild.firstChild.nodeValue));
				}
			});
			
			var rParams:Array = new Array();
			
			rParams.push( { method:method } );
			
			for (var key in reqParams) {
				rParams.push(reqParams[key]);
			}
			
			for each (key in options) {
				rParams.push(key);
			}
			
			if (_session != 0) {
				rParams.push( { session:_session } );
			}
			
			var request:URLRequest = new URLRequest(API.URL);
			request.method = API.REQUEST_METHOD;
			
			var vars:URLVariables = new URLVariables();
			
			for each (key in rParams) {
				for (var k in key) {
					vars[k] = key[k];
				}
			}
			
			vars.sig = generateSignature(rParams);
			
			vars.api_id = ID;
			
			request.data = vars;
			
			response.load(request);
			
		}
		
		private function receiveData(method:String, data:XML):void{
			switch (method) {
				case APIEvent.TIME:
					dispatchEvent(new APIEvent(APIEvent.TIME, data));
					break;
				case APIEvent.VARIABLE:
					dispatchEvent(new APIEvent(APIEvent.VARIABLE, data));
					break;
				case APIEvent.VARIABLES:
					dispatchEvent(new APIEvent(APIEvent.VARIABLES, data));
					break;
				case APIEvent.SCORES:
					dispatchEvent(new APIEvent(APIEvent.SCORES, data));
					break;
				case APIEvent.USER_INFO:
					dispatchEvent(new APIEvent(APIEvent.USER_INFO, data));
					break;
				case APIEvent.USER_INFO_EX:
					dispatchEvent(new APIEvent(APIEvent.USER_INFO_EX, data));
					break;
				case APIEvent.MESSAGE:
					
					var messages:Array = new Array();
					
					for each (var prop:XML in data.message_info) {
						
						messages.push({
							user_id:prop.user_id,
							user_name:prop.user_name,
							time:prop.time,
							message:prop.message
						});
						
					}
					
					if (USE_COMPRESSION) {
						
						for (var m in messages) {
							
							var ms:String = messages[m].message;
							
							var md:String = ms.slice(0, 32);
							var bd:String = ms.slice(32);
							var ba:ByteArray = Base64.decodeToByteArray(bd);
							
							if (Hex.fromArray(new MD5().hash(ba)) == md) {
								
								ba.uncompress();
								messages[m].message = ba.toString();
								
							} else {
								messages[m].message = "";
								trace("error!");
							}
						}
						
					}
					
					dispatchEvent(new APIEvent(APIEvent.MESSAGE, messages));
					
					break;
			}
		}
		
		private function sortByKeys(aInput:Array):Array {
			var aTemp:Array = new Array();
			for each (var sKey in aInput) {
				for (var k in sKey) {
					aTemp.push( { name:k, value:sKey[k] } );
				}
			}
			aTemp.sortOn("name");
			var aOutput = new Array();
			for (var i:Number = aTemp.length - 1; i >= 0; i--) {
				aOutput.unshift(aTemp[i]);
			}
			return aOutput;
		}
		
		private function generateSignature(reqParams:Array):String {
			
			var signature:String = "";
			var sorted:Array = sortByKeys(reqParams);
			
			for (var key = 0; key < sorted.length; key++ ) {
				var ckey = sorted[key];
				signature += ckey.name + "=" + ckey.value;
			}
			
			signature = VIEWER_ID + "api_id=" + ID + signature + SECRET;
			
			var bytes:ByteArray = new ByteArray();
			bytes.writeUTFBytes(signature);
			bytes = new MD5().hash(bytes);
			
			return Hex.fromArray(bytes);
		}
		
		public function get session():int { return _session; }
		
		public function set session(value:int):void {
			_session = value;
		}
		
		public function get sessionName():String { return _sessionName; }
		
		public function set sessionName(value:String):void {
			
			// sendRequest(API.PUT_VARIABLE, { key:API.V_SESSION_NAME, value:value } );
			
			// TODO:
			// Добавить возврат имени по запросу
			// Добавить Dictionary для имен сессий (кэширование)
			
			_sessionName = value;
		}
		
		
		public function clone():APIConnector {
			return new APIConnector(vars, ID, SECRET, TEST_VIEWER, TEST_MODE, USE_COMPRESSION);
		}
		
	}
	
}